/******************************************************************************
 * Copyright 2022 The Airos Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/

#include "base/device_connect/camera/device_factory.h"
#include "yaml-cpp/yaml.h"

#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <vector>

using os::v2x::device::CameraData;
using os::v2x::device::CameraDevice;
using os::v2x::device::CameraDeviceFactory;

#define SHOW_DASH " ################## "
#define SHOW_TITLE(name) SHOW_DASH name SHOW_DASH

static void stream_proc(const std::string& camera_name, const uint8_t* data,
                        size_t size) {
  std::cout << SHOW_TITLE("CameraStream") << std::endl;
  std::cout << "camera_name: " << camera_name << std::endl;
  std::cout << "len: " << size << std::endl;
  std::cout << "data: ";
  for (size_t i = 0; i < size && i < 10; ++i) {
    std::cout << static_cast<unsigned int>(*(data + i)) << "|";
  }
  std::cout << std::endl;
}

static void show_camera_data(const std::shared_ptr<const CameraData>& data) {
  std::cout << SHOW_TITLE("CameraData") << std::endl;
  std::cout << "image_data: ";
  const char* image_data = (const char*)(data->image->data());
  for (size_t i = 0; i < data->image->size() && i < 10; ++i) {
    std::cout << static_cast<unsigned int>(*(image_data + i)) << "|";
  }
  std::cout << std::endl;
  std::cout << "device_type: " << static_cast<int>(data->device_type)
            << std::endl;
  std::cout << "device_id: " << data->device_id << std::endl;
  std::cout << "camera_type: " << static_cast<int>(data->camera_type)
            << std::endl;
  std::cout << "camera_name: " << data->camera_name << std::endl;
  std::cout << "image_mode: " << static_cast<int>(data->mode) << std::endl;
  std::cout << "height: " << data->height << std::endl;
  std::cout << "width: " << data->width << std::endl;
  std::cout << "timestamp: " << data->timestamp << std::endl;
  std::cout << "sequence_num: " << data->sequence_num << std::endl;
}

bool parse_config(const std::string& ex_conf, std::string& type_name,
                  std::string& camera_name, std::string& camera_conf) {
  try {
    YAML::Node node = YAML::LoadFile(ex_conf);
    type_name = node["device_type"].as<std::string>();
    camera_name = node["camera_name"].as<std::string>();
    camera_conf = node["camera_config"].as<std::string>();
  } catch (const std::exception& err) {
    std::cerr << err.what() << std::endl;
    return false;
  } catch (...) {
    std::cerr << "Parse yaml config failed: " << ex_conf << std::endl;
    return false;
  }

  return true;
}

int main(int argc, char** argv) {
  if (argc < 2) {
    std::cout
        << "Args Error!\nUsage: camera_test_tool conf_file1 conf_file2 ..."
        << std::endl;
    return 1;
  }

  std::map<std::string, std::shared_ptr<CameraDevice>> device_type_map;
  std::map<std::string, std::vector<std::string>> device_num_map;
  std::string device_type, camera_name, camera_conf;
  for (int i = 1; i < argc; ++i) {
    if (!parse_config(argv[i], device_type, camera_name, camera_conf)) {
      std::cerr << "Parse conf_file failed! skip: " << argv[i] << std::endl;
      continue;
    }
    if (!device_type_map.count(device_type)) {
      auto device =
          CameraDeviceFactory::Instance().GetShared(device_type, stream_proc);
      if (!device) {
        std::cerr << "Build device instance failed! skip: " << device_type
                  << std::endl;
        continue;
      }
      device_type_map[device_type] = device;
    }
    auto device = device_type_map[device_type];
    if (!device) continue;
    if (!device->Init(camera_conf)) {
      std::cerr << "Init " << camera_name << " failed! skip: " << camera_name
                << std::endl;
      continue;
    }
    device_num_map[device_type].emplace_back(camera_name);
  }

  std::map<std::string, std::unique_ptr<std::thread>> task_map;
  for (const auto& item : device_num_map) {
    device_type = item.first;
    auto device = device_type_map[device_type];
    for (const auto& camera : item.second) {
      task_map.emplace(camera, new std::thread([device, camera]() {
                         while (true) {
                           auto data = device->GetImage(camera);
                           show_camera_data(data);
                         }
                       }));
    }
  }

  for (auto& item : task_map) {
    auto& task = item.second;
    if (task->joinable()) {
      task->join();
    }
  }

  return 0;
}
